package xin.marcher.module.evaluate.repository;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.*;
import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Repository;
import xin.marcher.module.common.core.PageResult;
import xin.marcher.module.common.enums.DelFlagEnum;
import xin.marcher.module.common.enums.YesOrNoEnum;
import xin.marcher.module.common.enums.inventory.InventoryExceptionCode;
import xin.marcher.module.common.exception.BaseBizException;
import xin.marcher.module.common.exception.CommonErrorCodeEnum;
import xin.marcher.module.evaluate.converter.ItemScoreWeightConfigurationConverter;
import xin.marcher.module.evaluate.converter.ShopScoreWeightConfigurationConverter;
import xin.marcher.module.evaluate.domain.dto.LineChartDTO;
import xin.marcher.module.evaluate.domain.entity.HandledItemCollectionDO;
import xin.marcher.module.evaluate.domain.entity.ItemCollectionDO;
import xin.marcher.module.evaluate.domain.entity.ItemScoreWeightConfigurationDO;
import xin.marcher.module.evaluate.domain.entity.ShopScoreWeightConfigurationDO;
import xin.marcher.module.evaluate.domain.request.EvaluateQueryRequest;
import xin.marcher.module.evaluate.domain.request.ItemScoreWeightConfigurationRequest;
import xin.marcher.module.evaluate.domain.request.QueryProductPageRequest;
import xin.marcher.module.evaluate.domain.request.ShopScoreWeightConfigurationRequest;
import xin.marcher.module.evaluate.mapper.HandledItemCollectionMapper;
import xin.marcher.module.evaluate.mapper.ItemCollectionMapper;
import xin.marcher.module.evaluate.mapper.ItemScoreWeightConfigurationMapper;
import xin.marcher.module.evaluate.mapper.ShopScoreWeightConfigurationMapper;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

/**
 *
 */
@Slf4j
@Repository
public class EvaluateRepository {

    private static final String HANDLED_ITEM_INDEX_NAME = "handled_item_index";

    @Resource
    private ItemCollectionMapper itemCollectionMapper;

    @Resource
    private HandledItemCollectionMapper handledItemCollectionMapper;

    @Resource
    private ShopScoreWeightConfigurationMapper shopScoreWeightConfigurationMapper;

    @Resource
    private ShopScoreWeightConfigurationConverter shopScoreWeightConfigurationConverter;

    @Resource
    private ItemScoreWeightConfigurationMapper itemScoreWeightConfigurationMapper;

    @Resource
    private ItemScoreWeightConfigurationConverter itemScoreWeightConfigurationConverter;

    @Resource
    private RestHighLevelClient restHighLevelClient;

    /**
     * 根据itemKey分页查询初始item数据
     *
     * @param request
     * @return
     */
    public PageResult<ItemCollectionDO> queryByItemKey(EvaluateQueryRequest request) {
        LambdaQueryWrapper<ItemCollectionDO> queryWrapper = Wrappers.lambdaQuery();
        // 商品关键词
        queryWrapper.eq(StringUtils.isNoneBlank(request.getItemKey()), ItemCollectionDO::getItemKey, request.getItemKey());
        // 收集日期
        queryWrapper.ge(Objects.nonNull(request.getCollectTime()), ItemCollectionDO::getCollectTime, request.getCollectTime());
        // 数据是否有效

        queryWrapper.eq(Objects.nonNull(request.getDelFlag()), ItemCollectionDO::getDelFlag, request.getDelFlag());
        Page<ItemCollectionDO> page = new Page<>(request.getPageNo(), request.getPageSize());
        Page<ItemCollectionDO> pageResult = itemCollectionMapper.selectPage(page, queryWrapper);
        return new PageResult<>(pageResult);
    }

    /**
     * 根据id将一批数据设置为失效状态
     *
     * @param ids
     */
    public void invalidItemCollection(List<Long> ids) {
        int count = itemCollectionMapper.delItemCollectionById(ids, YesOrNoEnum.NO.getCode());
        if (count <= 0) {
            throw new BaseBizException(CommonErrorCodeEnum.SQL_ERROR);
        }
    }

    /**
     * 创建店铺评分权重
     *
     * @param request
     */
    public long createShopScoreWeightConfiguration(ShopScoreWeightConfigurationRequest request) {
        // 对象转换
        ShopScoreWeightConfigurationDO shopScoreWeightConfigurationDO = shopScoreWeightConfigurationConverter.requestToEntity(request);
        shopScoreWeightConfigurationDO.initCommon();

        int count = shopScoreWeightConfigurationMapper.insert(shopScoreWeightConfigurationDO);
        if (count <= 0) {
            throw new BaseBizException(InventoryExceptionCode.INVENTORY_SQL);
        }

        return shopScoreWeightConfigurationDO.getId();
    }

    /**
     * 获取配置信息，默认情况下只能有一条生效的配置信息
     *
     * @return
     */
    public ItemScoreWeightConfigurationDO queryItemScoreWeight() {
        LambdaQueryWrapper<ItemScoreWeightConfigurationDO> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(ItemScoreWeightConfigurationDO::getDelFlag, YesOrNoEnum.YES.getCode());
        ItemScoreWeightConfigurationDO itemScoreWeightConfigurationDO =
                itemScoreWeightConfigurationMapper.selectOne(queryWrapper);
        return itemScoreWeightConfigurationDO;
    }

    /**
     * 获取配置信息，默认情况下只能有一条生效的配置信息
     *
     * @return
     */
    public ShopScoreWeightConfigurationDO queryShopScoreWeight() {
        LambdaQueryWrapper<ShopScoreWeightConfigurationDO> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(ShopScoreWeightConfigurationDO::getDelFlag, YesOrNoEnum.YES.getCode());
        ShopScoreWeightConfigurationDO shopScoreWeightConfigurationDO =
                shopScoreWeightConfigurationMapper.selectOne(queryWrapper);
        return shopScoreWeightConfigurationDO;
    }

    /**
     * 批量插入到处理后结果集中
     *
     * @param batch
     */
    public void batchInsertHandledItem(List<HandledItemCollectionDO> batch) {
        int count = handledItemCollectionMapper.insertBatch(batch);
        if (count <= 0) {
            throw new BaseBizException(CommonErrorCodeEnum.SQL_ERROR);
        }
    }

    /**
     * 创建商品评分权重
     *
     * @param request
     */
    public long createItemScoreWeightConfiguration(ItemScoreWeightConfigurationRequest request) {
        // 对象转换
        ItemScoreWeightConfigurationDO itemScoreWeightConfigurationDO = itemScoreWeightConfigurationConverter.requestToEntity(request);
        itemScoreWeightConfigurationDO.initCommon();

        int count = itemScoreWeightConfigurationMapper.insert(itemScoreWeightConfigurationDO);
        if (count <= 0) {
            throw new BaseBizException(InventoryExceptionCode.INVENTORY_SQL);
        }

        return itemScoreWeightConfigurationDO.getId();
    }

    /**
     * 批量插入到处理后结果集中(ES)
     *
     * @param list
     */
    public void batchInsertHandledItemToEs(List<HandledItemCollectionDO> list) {
        String indexName = HANDLED_ITEM_INDEX_NAME + DateFormatUtils.format(new Date(), "_yyyy_MM");
        BulkRequest bulkRequest = new BulkRequest(indexName);
        for (HandledItemCollectionDO e : list) {
            // HandledItemCollectionDO中的字段
            Map<String, Object> jsonMap = new HashMap<>();
            jsonMap.put("itemKey", e.getItemKey());
            jsonMap.put("itemId", e.getItemId());
            jsonMap.put("itemName", e.getItemName());
            jsonMap.put("price", e.getPrice());
            jsonMap.put("discountPrice", e.getDiscountPrice());
            jsonMap.put("categoryId", e.getCategoryId());
            jsonMap.put("categoryName", e.getCategoryName());
            jsonMap.put("itemScore", e.getItemScore());
            jsonMap.put("shopScore", e.getShopScore());
            jsonMap.put("goodCommentRate", e.getGoodCommentRate());
            jsonMap.put("badCommentRate", e.getBadCommentRate());
            jsonMap.put("recommendedLevel", e.getRecommendedLevel());
            jsonMap.put("dataChannel", e.getDataChannel());
            jsonMap.put("recommendedReason", e.getRecommendedReason());
            jsonMap.put("collectTime", e.getCollectTime());
            // 额外的字段
            jsonMap.put("collectDate", DateFormatUtils.format(e.getCollectTime(), "yyyy-MM-dd"));
            bulkRequest.add(new IndexRequest().source(jsonMap));
        }

        try {
            restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("批量插入到处理后结果集中(ES)失败，list={}", JSON.toJSONString(list));
        }
    }

    /**
     * 查询曲线
     * （1）历史标价曲线
     * （2）历史实际价格曲线
     * （3）商品历史评分曲线
     * （4）店铺历史评分曲线
     *
     * @param minDate 格式为 2022-04-01
     * @param maxDate 格式为 2022-04-01
     */
    public List<LineChartDTO> queryLineChart(String itemId, String minDate, String maxDate) throws IOException {
        // 使用了es的动态索引，精确查询使用自动生成的xxx.keyword字段，如果手动创建索引指定了itemId的类型为keyword，则可以只接用itemId
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("itemId.keyword", itemId);
        // 聚合，正常情况每个itemId每天只有一条数据，如果有多条数据取其求平均值为当前itemId在当前日期的结果
        DateHistogramAggregationBuilder dateHistogramAggregationBuilder = AggregationBuilders.dateHistogram("lines")
                .field("collectDate")
                .calendarInterval(DateHistogramInterval.DAY)
                .format("yyyy-MM-dd")
                .minDocCount(0)
                .extendedBounds(new LongBounds(minDate, maxDate))
                .subAggregation(AggregationBuilders.avg("price").field("price"))
                .subAggregation(AggregationBuilders.avg("discountPrice").field("discountPrice"))
                .subAggregation(AggregationBuilders.avg("itemScore").field("itemScore"))
                .subAggregation(AggregationBuilders.avg("shopScore").field("shopScore"));

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(termQueryBuilder)
                .aggregation(dateHistogramAggregationBuilder)
                // 不要返回查询到的文档数据
                .size(0);
        SearchRequest searchRequest = new SearchRequest(HANDLED_ITEM_INDEX_NAME + "*");
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        // response的结构
        // {
        //     "aggregations":{
        //         "asMap":{
        //             "lines":{
        //                 "buckets":[
        //                     某一天的数据
        //                     {
        //                         "aggregations":{
        //                             "asMap":{
        //                                 "itemScore":{
        //                                     "value":1,
        //                                 },
        //                                 "discountPrice":{
        //                                     "value":90,
        //                                 },
        //                                 "shopScore":{
        //                                     "value":1,
        //                                 },
        //                                 "price":{
        //                                     "value":100,
        //                                 }
        //                             },
        //                         },
        //                         "keyAsString":"2022-03-03"
        //                     },
        //                     下一天的数据
        //                 ]
        //             }
        //         }
        //     }
        // }
        ParsedDateHistogram dates = response.getAggregations().get("lines");
        List<LineChartDTO> result = new ArrayList<>();
        for (Histogram.Bucket bucket : dates.getBuckets()) {
            LineChartDTO e = new LineChartDTO();
            e.setDate(bucket.getKeyAsString());
            Aggregations aggregations = bucket.getAggregations();
            ParsedAvg price = aggregations.get("price");
            ParsedAvg discountPrice = aggregations.get("discountPrice");
            ParsedAvg itemScore = aggregations.get("itemScore");
            ParsedAvg shopScore = aggregations.get("shopScore");

            e.setPrice((int) Math.round(price.getValue()));
            e.setDiscountPrice((int) Math.round(discountPrice.getValue()));
            e.setItemScore((int) Math.round(itemScore.getValue()));
            e.setShopScore((int) Math.round(shopScore.getValue()));
            result.add(e);
        }
        return result;
    }

    /**
     * 根据itemKey搜索商品
     */
    public PageResult<HandledItemCollectionDO> queryProductPage(QueryProductPageRequest request) throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.trackTotalHits(true);
        // 搜索
        searchSourceBuilder.query(QueryBuilders.matchQuery("itemKey", request.getItemKey()));
        // 分页
        int from = (request.getPageNo() - 1) * request.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(request.getPageSize());
        // 排序
        searchSourceBuilder.sort(request.getSortFiledName(), SortOrder.fromString(request.getSortOrder()));

        SearchRequest searchRequest = new SearchRequest(HANDLED_ITEM_INDEX_NAME + "*");
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hits = searchResponse.getHits();

        return buildResult(hits, request.getPageNo(), request.getPageSize());
    }

    private PageResult<HandledItemCollectionDO> buildResult(SearchHits hits, int pageNum, int pageSize) {
        PageResult<HandledItemCollectionDO> result = new PageResult<>();
        result.setNumber(pageNum);
        result.setSize(pageSize);
        result.setTotalElements(hits.getTotalHits().value);
        List<HandledItemCollectionDO> content = new ArrayList<>();
        for (SearchHit hit : hits) {
            content.add(JSON.parseObject(hit.getSourceAsString(), HandledItemCollectionDO.class));
        }
        result.setContent(content);
        return result;
    }

    /**
     * 将其他配置信息修改为无效
     *
     * @param id
     */
    public void invalidShopScoreWeightConfiguration(long id) {
        LambdaUpdateWrapper<ShopScoreWeightConfigurationDO> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.ne(ShopScoreWeightConfigurationDO::getId, id);

        updateWrapper.set(ShopScoreWeightConfigurationDO::getDelFlag, DelFlagEnum.DISABLED.getCode());

        shopScoreWeightConfigurationMapper.update(null, updateWrapper);
    }

    /**
     * 将其他配置信息修改为无效
     *
     * @param id
     */
    public void invalidItemScoreWeightConfiguration(long id) {
        LambdaUpdateWrapper<ItemScoreWeightConfigurationDO> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.ne(ItemScoreWeightConfigurationDO::getId, id);

        updateWrapper.set(ItemScoreWeightConfigurationDO::getDelFlag, DelFlagEnum.DISABLED.getCode());

        itemScoreWeightConfigurationMapper.update(null, updateWrapper);
    }

    /**
     * 修改权重值
     *
     * @param request
     */
    public void updateItemScoreWeightConfiguration(ItemScoreWeightConfigurationRequest request) {
        LambdaUpdateWrapper<ItemScoreWeightConfigurationDO> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(ItemScoreWeightConfigurationDO::getId, request.getId());
        updateWrapper.eq(ItemScoreWeightConfigurationDO::getDelFlag, DelFlagEnum.EFFECTIVE.getCode());

        updateWrapper.set(Objects.nonNull(request.getSellCountWeight()), ItemScoreWeightConfigurationDO::getSellCountWeight,
                request.getSellCountWeight());
        updateWrapper.set(Objects.nonNull(request.getLikeCountWeight()), ItemScoreWeightConfigurationDO::getLikeCountWeight,
                request.getLikeCountWeight());
        updateWrapper.set(Objects.nonNull(request.getCommentCountWeight()), ItemScoreWeightConfigurationDO::getCommentCountWeight,
                request.getCommentCountWeight());
        updateWrapper.set(Objects.nonNull(request.getGoodCommentCountWeight()), ItemScoreWeightConfigurationDO::getGoodCommentCountWeight,
                request.getGoodCommentCountWeight());
        updateWrapper.set(Objects.nonNull(request.getBadCommentCountWeight()), ItemScoreWeightConfigurationDO::getBadCommentCountWeight,
                request.getBadCommentCountWeight());

        itemScoreWeightConfigurationMapper.update(null, updateWrapper);
    }

    /**
     * 修改权重值
     *
     * @param request
     */
    public void updateShopScoreWeightConfiguration(ShopScoreWeightConfigurationRequest request) {
        LambdaUpdateWrapper<ShopScoreWeightConfigurationDO> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(ShopScoreWeightConfigurationDO::getId, request.getId());
        updateWrapper.eq(ShopScoreWeightConfigurationDO::getDelFlag, DelFlagEnum.EFFECTIVE.getCode());

        updateWrapper.set(Objects.nonNull(request.getShopServiceScoreWeight()), ShopScoreWeightConfigurationDO::getShopServiceScoreWeight,
                request.getShopServiceScoreWeight());
        updateWrapper.set(Objects.nonNull(request.getShopLogisticsScoreWeight()), ShopScoreWeightConfigurationDO::getShopLogisticsScoreWeight,
                request.getShopLogisticsScoreWeight());
        updateWrapper.set(Objects.nonNull(request.getShopDescScoreWeight()), ShopScoreWeightConfigurationDO::getShopDescScoreWeight,
                request.getShopDescScoreWeight());

        shopScoreWeightConfigurationMapper.update(null, updateWrapper);
    }
}
